// Observation and calculated values
LOCAL sightType,bodyItem;
LOCAL dip_min, HE_ft, IC_min;
LOCAL horShort_nm, HS_deg, HO_deg;
LOCAL artificial,temp_C,press_mb;
LOCAL HA_deg; 

LOCAL DRlat_deg, DRlong_deg;
LOCAL altCorr_min, obs_UTC;
LOCAL dec_deg, decBefore_utc,decAfter_utc;
LOCAL GHABefore_UTC, GHA_deg;
LOCAL LHA_deg, Hc_deg, Z_deg;
LOCAL Zn_deg, a_nm;
LOCAL limbSign, limb, refr;
LOCAL semiD, paralX; 
LOCAL obsDate;
LOCAL rfDist_nm,rfBrg_deg;
LOCAL starId,SHA_deg;

// Default values for subsequent sights
LOCAL IC_default, DRlat_default:=45, DRlon_default:=-73,HE_default;
LOCAL date_default,temp_default,press_default;

//Moon sight variables
LOCAL GHATimeCorr_deg, GHAvcorr_min, decdcorr_min, HPcorr_min;
LOCAL tableGHA_deg,tableDec_deg;

// Utility variables
LOCAL timeZone:=-5;
LOCAL maxLOP:=9;
LOCAL current;
LOCAL nl:=CHAR(10);
LOCAL progressFlag:=1;

EXPORT SIGHT()
BEGIN  
  HAngle:=1;

  // Check that our variables are initialized
  IF TYPE(obs_UTC)=0 THEN RESET_SIGHTS(); END;

  IF NOT INPUT_HS_AND_BODY() THEN RETURN "Canceled."; END;
  
  LOCAL c;
  
  CASE 
    IF sightType(current)=1 THEN c:=SUN_SIGHT(); END;
    IF sightType(current)=2 THEN c:=MOON_SIGHT(); END;
    IF sightType(current)=3 THEN c:=STAR_SIGHT(); END;
    IF sightType(current)=4 THEN c:=PLANET_SIGHT(); END;
    IF sightType(current)=5 THEN c:=SUN_SIGHT_WITH_TABLES(); END;
    IF sightType(current)=6 THEN c:=MOON_SIGHT_WITH_TABLES(); END;
    IF sightType(current)=7 THEN
      c:=PRE_CALCULATED_SIGHT();
      // As the name implies, this is a pre-calculated
      // sight so no need to continue with the HC
      // and LOP calculations.
      IF NOT c THEN RETURN "Canceled."; ELSE RETURN; END;
    END;
  END;
  
  IF NOT c THEN RETURN "Canceled."; END;
  
  IF NOT HC_CALC(current) THEN RETURN "Canceled."; END;

  IF NOT VIEW_LOP_PARAMETERS(current) THEN RETURN "Canceled."; END;
  
  LOP_EQUATION(current);

END;

LOCAL SUN_SIGHT()
BEGIN
  HAngle:=1;

  HO_CALC_SUN(current);
  
  IF progressFlag THEN
    PRINT();
    PRINT("Calculating heliocentric Earth ephemeris...");
  END;
  
  LOCAL sunData:=SunEphemeris(obsDate(current),obs_UTC(current),progressFlag);
  
  dec_deg(current):=sunData(2);
  GHA_deg(current):=sunData(1);
  
  RETURN 1;
END;

LOCAL MOON_SIGHT()
BEGIN
  HAngle:=1;
    
  // Add a semi-diameter correction
  // for lower limb and remove it for upper
  limb(current):=-1*((limbSign(current)-1)*2-1);
  
  IF progressFlag THEN
    PRINT();
    PRINT("Calculating Moon ephemeris...");    
  END;
  
  LOCAL r:=MoonEphemeris(obsDate(current),obs_UTC(current));

  GHA_deg(current):=r(1);
  dec_deg(current):=r(2);

  // True altitude
  HO_deg(current):=HA_deg(current)-REFRACTION(current)/60;
  
  // Corrected altitude including semi-diameter
  // correction and parallax correction
  HO_deg(current):=HO_deg(current)
    +limb(current)*r(4)/3600
    +r(3)/3600*COS(HO_deg(current));
  
  RETURN 1;
END;

 
LOCAL PLANET_SIGHT()
BEGIN
  HAngle:=1;
 
  // Calculate altitude correction.
  // NOTE: the planet ephemeris function
  // returns the position of
  // the centroid of the illuminated disc for Venus
  // and Mars, so the Additional Correction in the
  // Almanac for Mars and Venus MUST NOT be applied.
  
  altCorr_min(current):=-1*REFRACTION(current);
  
  HO_deg(current):=HA_deg(current)+altCorr_min(current)/60;
  
  IF progressFlag THEN
    PRINT();
    PRINT("Calculating planet ephemeris...");
  END;  
  
  LOCAL r:=PlanetEphemeris(
             starId(current),
             obsDate(current),
             obs_UTC(current),
       1,
       progressFlag);
  
  dec_deg(current):=r(2);
  SHA_deg(current):=360-r(5);
  GHA_deg(current):=r(1);

  RETURN 1;
END;

LOCAL STAR_SIGHT()
BEGIN  
  HAngle:=1;
  
  altCorr_min(current):=-1*REFRACTION(current);
  HO_deg(current):=HA_deg(current)+altCorr_min(current)/60;
 
  IF progressFlag THEN
    PRINT();
    PRINT("Calculating star ephemeris...");
  END;

  LOCAL r:=StarEphemeris(
             starId(current),
             obsDate(current),
             obs_UTC(current));
       
  dec_deg(current):=r(2);
  SHA_deg(current):=360-r(3);
  GHA_deg(current):=r(1);

  RETURN 1;
END;

LOCAL SUN_SIGHT_WITH_TABLES()
BEGIN
  LOCAL hr,hrp1,nl,fn,f,xx,c;
 
  HO_CALC_SUN(current);

  hr:=" at "+STRING(IP(obs_UTC(current)))+":00:";
  hrp1:=" at "+STRING(IP(obs_UTC(current)+1))+":00:";
 
  c:=INPUT({decBefore_utc(current),
         decAfter_utc(current),
         GHABefore_UTC(current)},
         "LOP "+STRING(current)+" Altitude Computation",
        {"Dec."+hr,"Dec."+hrp1,
         "GHA"+hr},
        {"Tabled declination at "+hr+" (+=N -=S)",
         "Tabled declination at "+hrp1+" (+=N -=S)",
         "Tabled GHA at "+hr+" (°)"});
  IF NOT c THEN RETURN c; END;
  
  // Interpolate declination from tables
  dec_deg(current):=decBefore_utc(current)
      +(decAfter_utc(current)
        -decBefore_utc(current))
        *(FP(obs_UTC(current)));

  // Adjust GHA from whole hour table time
  // to observation time
  GHA_deg(current):=GHABefore_UTC(current)
                  +15*FP(obs_UTC(current));

  RETURN c;
END;

LOCAL MOON_SIGHT_WITH_TABLES()
BEGIN
  LOCAL c;
  
  c:=INPUT({tableGHA_deg(current),
           tableDec_deg(current)},
    "LOP "+STRING(current)+" Moon Almanac Data",
    {"GHA:",
     "Declination:"},
    {"GHA from Almanac (°)",
     "Declination from Almanac (°)"});
  IF NOT c THEN RETURN c; END;
  
  c:=INPUT({GHATimeCorr_deg(current), 
         GHAvcorr_min(current), 
         decdcorr_min(current),
         altCorr_min(current),
         HPcorr_min(current)},
    "LOP "+STRING(current)+" Moon Sight Corrections",
    {"GHA time correction:", 
     "GHA v correction:", 
     "Dec d correction:",
     "Alt correction:",
     "HP correction:"},
    {"From Increments & Corrections table (')",
     "From Increments & Corrections table (')",
     "From Increments & Corrections table (')",
     "Altitude correction for HA="+→DMD(HA_deg(current))+"(')",
     "Horizontal parallax correction (')"});
  IF NOT c THEN RETURN c; END;
  
  GHA_deg(current):=tableGHA_deg(current)
         +GHATimeCorr_deg(current)
         +GHAvcorr_min(current)/60;

  dec_deg(current):=tableDec_deg(current)
         +decdcorr_min(current)/60;

  HO_deg(current):=HA_deg(current)+HPcorr_min(current)/60
         +altCorr_min(current)/60;

  IF limbSign(current)=2 THEN 
    HO_deg(current):=HO_deg(current)-30/60;
  END;

  RETURN c;
END;

LOCAL PRE_CALCULATED_SIGHT()
BEGIN

  LOCAL c:=INPUT({a_nm(current),Zn_deg(current)},
        "LOP "+STRING(current)+" Pre-calculated values",
        {"A:","ZN:"},
        {"Distance from assumed position (nm)",
         "Azimuth to celestial body (°)"});
  
  IF NOT c THEN RETURN c; END;

  LOP_EQUATION(current);
  
  RETURN c;
END;

EXPORT PLOT_LOPs()
BEGIN
  HAngle:=1;

  Function.Xmin:=-180;
  Function.Xmax:=180;
  Function.Ymin:=-90;
  Function.Ymax:=90;
  LOCAL i:=1;
  
  STARTAPP("Function");
  
  // This doesn't work with a loop
  // for some reason
  FOR i FROM 0 TO 9 DO    
  IF a_nm(IFTE(i=0,10,i))<>0 THEN Function.CHECK(i); END;
  END;
  
  STARTVIEW(1,1);  
  
END;

VIEW_LOP_PARAMETERS()
BEGIN
  LOCAL c:=SELECT_LOP(current,"Select LOP to view");
  IF c=0 THEN RETURN "Canceled."; END;

  VIEW_LOP_PARAMETERS(c);
END;

VIEW_LOP_PARAMETERS(c)
BEGIN
  LOCAL nl:=CHAR(10);
  
  IF NOT MSGBOX("Ho  : "+→DMD(HO_deg(c))+nl+
         "GHA : "+→DMD(GHA_deg(c))+nl+
         "Dec.: "+→DMD(dec_deg(c))+nl+
         "LHA : "+→DMD(LHA_deg(c))+nl+
         "Hc  : "+→DMD(Hc_deg(c)),1) THEN RETURN 0; END;

  IF NOT MSGBOX("LOP: " + c + nl
         + "a (nm): "+IFTE(ABS(a_nm(c))<0.05,"0.0",STRING(HMS→(ABS(a_nm(c))),2,1)
           +IFTE(a_nm(c)≤0," AWAY"," TOWARD")) + nl
         + "Zn (°): "+STRING(Zn_deg(c),2,1) + nl
         + "AP lat (°): "+→DMD(DRlat_deg(c)) + nl
         + "AP lon (°): "+→DMD(DRlong_deg(c)),1) THEN RETURN 0; END;
  
  RETURN 1;
  
END;

EXPORT SHIFT_LOP()
BEGIN
  LOCAL f,index,fn;
  LOCAL d_nm,brg_deg;

  HAngle:=1;

  LOCAL c:=SELECT_LOP(current,"Select LOP to shift");  
  IF c=0 THEN RETURN "Canceled."; END;
 
  LOCAL rv:=INPUT({rfBrg_deg(c),
         rfDist_nm(c)},
    "LOP "+STRING(c)+" Shift",
    {"Bearing:","Distance:"},
    {"Bearing direction for shift (°)",
     "Distance of shift (nm)"
     });
  IF rv=0 THEN RETURN "Canceled."; END;

  fn:=c;
  // If register 10 is chosen, it
  // must be retrieved from Function F0
  IF fn==10 THEN fn:=0; END;

  // This is probably only valid around the equator
  LOCAL dx:=rfDist_nm(c)/60*SIN(rfBrg_deg(c));
  LOCAL dy:=rfDist_nm(c)/60*COS(rfBrg_deg(c));
  
  // Let's redo this with better calc instead
  //@TODO: TEST ACCURACY!
  LOCAL newCoords:=atdz(DRlat_deg(c),DRlong_deg(c),rfDist_nm(c),rfBrg_deg(c));
  dy:=newCoords(1)-DRlat_deg(c);
  dx:=newCoords(2)-DRlong_deg(c);
  
  // Retrieve LOP formula and use x
  // instead of the reserved X (which always
  // has an assigned value) and add
  // offsets using CAS    
  
  // Make sure x is not set  
  CAS("PURGE(x)");
  f:="CAS(\"Function.F"+STRING(c)+"(x-dx)+dy\");";
  f:=STRING(EXPR(f));

  // Replace x with X using string
  // manipulation to ensure X is not
  // evaluated to its assigned value
  index:=INSTRING(f,"x"); 
  f:=LEFT(f,index-1)+"'X'"
     + MID(f,index+1);

  LOCAL d:=SELECT_LOP(c,"Select destination LOP register");
  IF d=0 THEN RETURN "Canceled."; END;
  IF d=10 THEN d:=0; END;

  // Store LOP equation in destination
  // formula register
  f:="Function.F"+STRING(d)+":="+f;

  EXPR(f);      

  // Set a_nm to a non-zero value in the destination
  // LOP register so the shifted line will be displayed
  // in PLOT_LOPs
  a_nm(d):=1e-12;
  
  RETURN "Shifted LOP #"+STRING(c)+" by "
         +→DMD(dy)+" lat. and "+→DMD(dx)+" lon."
         +" into LOP #"+STRING(d);
END;

// Defining local variables external to function 
// so the last defined values remain valid
// between invocations
LOCAL ephLat,ephLon,ephStar:={1,15,30,45};

LOCAL OUTPUT_EPHEMERIS(bodyName,eph,lat,lon)
BEGIN
  LOCAL c:=HC_AZ(eph(1),eph(2),lat,lon);  
  
  PRINT(bodyName);
  PRINT("  GHA:"+→DMD(eph(1)));
  PRINT("  DEC:"+→DMD(eph(2)));
  PRINT("  HC :"+→DMD(c(1)));
  PRINT("  AZ :"+→DMD(c(2)));  
END;

EXPORT EPHEMERIDES()
BEGIN
  LOCAL date_:=Date;
  LOCAL time_:=GET_UTC_TIME();
  LOCAL i;
  
  HAngle:=1;
  ephLat:=DRlat_default;
  ephLon:=DRlon_default;
  
  LOCAL c:=INPUT(
    {date_,time_,ephLat,ephLon,
    {ephStar(1),CAS("col(navstar_,1)")},
    {ephStar(2),CAS("col(navstar_,1)")},
    {ephStar(3),CAS("col(navstar_,1)")},
    {ephStar(4),CAS("col(navstar_,1)")}},
     "Ephemeris",
    {"Date:","Time:","Latitude:","Longitude:","Star:","Star:","Star:","Star:"},
    {"Date of ephemerides (YYYY.MMDD)",
     "UTC Time (HH°MM'DD\")",
     "Latitude (°)","Longitude (°)",
   "Star to include in results",
     "Star to include in results",
     "Star to include in results",
     "Star to include in results"});
   
  IF NOT c THEN RETURN "Canceled."; END;

  ephLon:=NORMALIZE_LONGITUDE(ephLon);
  
  DRlat_default:=ephLat;
  DRlon_default:=ephLon;
  
  PRINT();
  
  c:=SunEphemeris(date_,time_,0);
  
  PRINT("Aries:");
  PRINT("  GHA:"+→DMD(c(3)));
  
  OUTPUT_EPHEMERIS("Sun",c,ephLat,ephLon);
  PRINT("  SD :"+c(4)+"\"");  
  
  OUTPUT_EPHEMERIS("Moon",MoonEphemeris(date_,time_),ephLat,ephLon);
  
  FOR i FROM 1 TO 4 DO
    c:=PlanetEphemeris(i,date_,time_,0,0);
    OUTPUT_EPHEMERIS(planets_(i),c,ephLat,ephLon);
  // Output the semi-diameter of Mars and Venus
  IF i<=2 THEN
    PRINT("  SD :"+c(4)+"\"");
  END;
  END;

  FOR i FROM 1 TO 4 DO
    OUTPUT_EPHEMERIS(
      navstar_(ephStar(i),1),
    StarEphemeris(ephStar(i),date_,time_),
    ephLat,
    ephLon);
  END;
END;

LOCAL minElevAngle_deg:=15,minAz_deg:=0,maxAz_deg:=360;

LOCAL WRITELINE(lin,body,gha,dec,sd,lat,lon,minElevAngle,minAz,maxAz)
BEGIN
  LOCAL c:=HC_AZ(gha,dec,lat,lon);
  
  IF c(1)>=minElevAngle THEN
    IF ((minAz<maxAz) AND (c(2)>=minAz) AND (c(2)<=maxAz))
     OR ((minAz>maxAz) AND ((c(2)>=minAz) OR (c(2)<=maxAz))) THEN
    EXPR("Spreadsheet.A"+lin+":=\""+body+"\"");
    EXPR("Spreadsheet.B"+lin+":="+c(2));
    EXPR("Spreadsheet.C"+lin+":="+c(1));
    EXPR("Spreadsheet.D"+lin+":="+gha);
    EXPR("Spreadsheet.E"+lin+":="+dec);
    IF sd<>0 THEN EXPR("Spreadsheet.F"+lin+":="+sd); END;    
    RETURN 1;
  END;
  END;
  RETURN 0;  
END;

EXPORT LIST_VISIBLE_BODIES()
BEGIN
  LOCAL date_:=Date;
  LOCAL time_:=GET_UTC_TIME();
  LOCAL i,d;
  LOCAL lin:=3;
  
  HAngle:=1;
  
  ephLat:=DRlat_default;
  ephLon:=DRlon_default;
  
  LOCAL c:=INPUT(
    {date_,time_,ephLat,ephLon,minElevAngle_deg,minAz_deg,maxAz_deg},
     "Ephemeris",
    {"Date:","Time:","Latitude:","Longitude:","Elevation:",
   "Azimuth from:","Clockwise to:"},
    {"Date (YYYY.MMDD)",
     "UTC Time (HH°MM'DD\")",
     "Latitude (°)","Longitude (°)",
   "Minimum elevation angle (°)",
   "Start of azimuth arc (°)",
   "End of azimuth arc (°)"});
   
  IF NOT c THEN RETURN "Canceled."; END;

  ephLon:=NORMALIZE_LONGITUDE(ephLon);
  
  DRlat_default:=ephLat;
  DRlon_default:=ephLon;
    
  Spreadsheet.A1:="Body";
  Spreadsheet.B1:="Azimuth";
  Spreadsheet.C1:="Elev (Hc)";
  Spreadsheet.D1:="GHA";
  Spreadsheet.E1:="Dec";
  Spreadsheet.F1:="Semi D";
  
  PRINT();
  
  c:=SunEphemeris(date_,time_,1);
  lin:=lin+WRITELINE(lin,"Sun",c(1),c(2),c(4),
       ephLat,ephLon,minElevAngle_deg,minAz_deg,maxAz_deg);    

  PRINT("Computing Moon");
  c:=MoonEphemeris(date_,time_);
  lin:=lin+WRITELINE(lin,"Moon",c(1),c(2),c(4),
       ephLat,ephLon,minElevAngle_deg,minAz_deg,maxAz_deg);    
  
  FOR i FROM 1 TO 4 DO        
    c:=PlanetEphemeris(i,date_,time_,0,1);
  lin:=lin+WRITELINE(lin,planets_(i),c(1),c(2),c(4),
                     ephLat,ephLon,minElevAngle_deg,minAz_deg,maxAz_deg);    
  END;
  
  PRINT("Computing stars");
  d:=StarEphemerisAll(date_,time_);
  
  FOR i FROM 1 TO SIZE(d) DO      
    c:=d(i);    
    lin:=lin+WRITELINE(lin,navstar_(i,1),c(1),c(2),0,
                     ephLat,ephLon,minElevAngle_deg,minAz_deg,maxAz_deg);    
  END;
  
  FOR i FROM lin TO 100 DO
  EXPR("Spreadsheet.A"+i+":=\"\"");  
  EXPR("Spreadsheet.B"+i+":=\"\"");
  EXPR("Spreadsheet.C"+i+":=\"\"");
  EXPR("Spreadsheet.D"+i+":=\"\"");
  EXPR("Spreadsheet.E"+i+":=\"\"");
  EXPR("Spreadsheet.F"+i+":=\"\"");
  END;
  
  PRINT("Press any key to view results.");

  STARTAPP("Spreadsheet");

END;


LOP_EQUATION(index)
BEGIN
  LOCAL c:=atdz(DRlat_deg(index),DRlong_deg(index),
                a_nm(index),Zn_deg(index));

  LOCAL f:=−1/TAN(90-Zn_deg(index))
            *COS(DRlat_deg(index))
            *('X'-c(2))+c(1);

  LOCAL i:=index;
  IF i=10 THEN i:=0; END;
  EXPR("Function.F"+i+":=f");
END;


SELECT_LOP(default,prompt,askProgress)
BEGIN
  LOCAL lop:=default,r;

  IF askProgress THEN
    r:=INPUT({{lop,{"1","2","3","4","5",
       "6","7","8","9","10"}},
       {progressFlag,1}},
       "LOP register",
       {"LOP:","Show progress"},
       {prompt,
        "Select to display calculation progress"});
  ELSE
    r:=INPUT({{lop,{"1","2","3","4","5",
       "6","7","8","9","10"}}},
       "LOP register",
       {"LOP:"},
       {prompt});
  END;
  
  IF r=0 THEN RETURN 0;
  ELSE RETURN lop;
  END;
END;

SELECT_LOP(default,prompt)
BEGIN
  RETURN SELECT_LOP(default,prompt,0);
END;

SELECT_LOP(default)
BEGIN
  RETURN SELECT_LOP(default,"Select LOP register");
END;

AVERAGE_READINGS()
BEGIN  
  LOCAL i:=1,more:=1,c,t,r;
  LOCAL hs:={},hourOffset:=0,scalingFactor:=10000;
  
  WHILE more DO
    PRINT();
    PRINT("OBSERVATION #"+i);
    PRINT("Press any key to record the time");
    PRINT("when the observation is made and");
    PRINT("enter the sextant reading.");
    WAIT(-1);
  
    t:=GET_UTC_TIME();
    
    r:=0;
    c:=INPUT({r,t},"Observation #"+i,
      {"HS:","Time:"},
    {"Enter sextant reading (°), or 0 to end",
     "UTC Time (HH°MM'SS\")"});

    IF ((c=0) OR (r=0)) AND (i=1) THEN RETURN {}; END;
  
    IF r>0 THEN
      more:=1;
    // Remove hour part of time and expand
    // time readings to increase the
    // accuracy of statistical computations
    IF hourOffset=0 THEN hourOffset:=IP(t); END;
    t:=scalingFactor*(t-hourOffset);  
      hs:=CONCAT(hs,{{t,r}});
      i:=i+1;
    ELSE
      i:=i-1;
      more:=0
    END;  
  END;
  
  // If only one observation was entered,
  // we return that value
  
  IF i=1 THEN     
    RETURN {hs(1,1)/scalingFactor+hourOffset,hs(1,2)};
  END;
  
  // Otherwise we perform a regression
  // and interpolate a value on a regression
  // curve, and display the results
  
  STARTAPP("Statistics_2Var");
  Statistics_2Var.C1:={};
  Statistics_2Var.C2:={};
  
  FOR t FROM 1 TO i DO
    Statistics_2Var.C1:=CONCAT(Statistics_2Var.C1,{hs(t,1)});
    Statistics_2Var.C2:=CONCAT(Statistics_2Var.C2,{hs(t,2)});
  END;
  
  // Select linear regression if 3 or fewer
  // data points, cubic otherwise
  //@TODO: Check if a trigonometric regression
  //wouldn't be better
  IF SIZE(Statistics_2Var.C1)<4 THEN r:=1; ELSE r:=9; END;
  
  Statistics_2Var.Fit:=1;
  Statistics_2Var.S1:={"C1","C2","",r,"",#FF,4,0};
  Statistics_2Var.CHECK(1);
  
  // Compute graph zoom factors
  LOCAL factor:=(MAX(Statistics_2Var.C1)-MIN(Statistics_2Var.C1))*1.1;  
  Statistics_2Var.Xmax:=MAX(Statistics_2Var.C1)+factor;
  Statistics_2Var.Xmin:=MIN(Statistics_2Var.C1)-factor;
  
  factor:=(MAX(Statistics_2Var.C2)-MIN(Statistics_2Var.C2))*1.1;
  Statistics_2Var.Ymax:=MAX(Statistics_2Var.C2)+factor;
  Statistics_2Var.Ymin:=MIN(Statistics_2Var.C2)-factor;
    
  LOCAL fitOptions:={
    "Linear",
    IFTE(i>2,"Quad",""), 
    IFTE(i>3,"Cubic",""),
    IFTE(i>3,"Trig",""),
    "Cancel",
    "Ok"};
  LOCAL fitTypes:={1,8,9,11};

  LOCAL avgTime,avgHs;
      
  REPEAT
    // Determine interpolated sight. If
  // the regression function has an extremum
  // within our observed time range, use that
  // extremum as our interpolated point.
  // Otherwise, use the midpoint of the time
  // range.
  
  // Extremums only exist for quadratic or
  // cubic regressions
  LOCAL extremumFlag:=0;
    IF Statistics_2Var.S1(4)=8 OR Statistics_2Var.S1(4)=9 THEN
    // Find the extremums of the regression function    
    r:=CAS("solve(eval(diff(subst(Statistics_2Var.S1(5),'X'=x),x))=0,x)");         
    // Check that they fall within our time range
    IF SIZE(r)>0 THEN
      FOR i FROM 1 TO SIZE(r) DO
      IF (r(i)>Statistics_2Var.C1(1)) AND (r(i)<Statistics_2Var.C1(0)) THEN
        extremumFlag:=1;
        avgTime:=r(i);
      END;
    END;
    ELSE
      avgTime:=Statistics_2Var.C1(1)+(Statistics_2Var.C1(0)-Statistics_2Var.C1(1))/2;
      END;
  ELSE
    avgTime:=Statistics_2Var.C1(1)+(Statistics_2Var.C1(0)-Statistics_2Var.C1(1))/2;
  END;
  avgHs:=PredY(avgTime);
    Statistics_2Var.C3:={avgTime,avgTime};
    Statistics_2Var.C4:={avgHs,avgHs};
    Statistics_2Var.S2:={"C3","C4","",13,"",#FF0000,2,#FF0000};
    Statistics_2Var.CHECK(2);  
    
  // Show graph and results
  STARTVIEW(1,1);
  TEXTOUT_P("Review results and click Ok to continue",G0,1,10,1,RGB(1,0,0));
    TEXTOUT_P("Interpolated values: "+HMS(avgTime/scalingFactor+hourOffset)+" - "+→DMD(avgHs)
    +IFTE(extremumFlag," (extremum)"," (midpoint)"),G0,1,25,1,RGB(1,0,0));
    
  // Monitor soft keys and include
  // indicator next to currently
  // selected key
  LOCAL flags:={0,0,0,0,0,0};
  flags(POS(fitTypes,Statistics_2Var.S1(4))):=1;
    c:=GET_SOFTKEY(fitOptions,flags);
  IF c=5 OR c=0 THEN 
    STARTVIEW(-7,1); 
    RETURN {};
  END;
  IF c>0 AND c<5 THEN
    IF fitOptions(c)<>"" THEN
      Statistics_2Var.S1:={"C1","C2","",fitTypes(c),"",#FF,4,0};
        Statistics_2Var.CHECK(1);
      STARTVIEW(1,1);        
    END;
  END;
  UNTIL c=6;

  // Go back to program view (where
  // the user likely was when the program
  // was started)
  STARTVIEW(-7,1);

  RETURN {→HMS(avgTime/scalingFactor)+hourOffset,→HMS(avgHs)}; 
END;

INPUT_HS_AND_BODY()
BEGIN
  LOCAL c:=SELECT_LOP(current,"Select LOP register",1);
  IF NOT c THEN RETURN c; END;

  current:=c;

  // OBSERVATION PARAMETERS

  LOCAL tempTime:=obs_UTC(current);
  LOCAL tempDate:=obsDate(current);

  IF tempDate=0 THEN tempDate:=date_default; END;
  IF tempTime=0 THEN 
    tempTime:=GET_UTC_TIME();
    IF tempTime>24 THEN tempTime:=tempTime-24; END;
    IF tempTime<0 THEN tempTime:=tempTime+24; END;
  END;


  IF DRlat_deg(current)=0 THEN DRlat_deg(current):=DRlat_default; END;
  IF DRlong_deg(current)=0 THEN DRlong_deg(current):=DRlon_default; END;  

  // If we already have a value for HS, set
  // default to not go through the observation
  // averaging process
  LOCAL avgReadings:=IFTE(HS_deg(current)=0,1,0);
  LOCAL bodiesList:=CONCAT({
    "Sun Lower Limb", "Sun Upper Limb", "Sun with Almanac tables",
    "Moon Lower Limb", "Moon Upper Limb", "Moon with Almanac tables",
    "Mars", "Venus", "Jupiter", "Saturn", "Pre-calculated sight"
    },CAS("col(navstar_,1)"));

  c:=INPUT({tempDate,
    DRlat_deg(current),
    DRlong_deg(current),         
    {bodyItem(current),bodiesList},
    {artificial(current),1},
    {avgReadings,1}},
    "LOP #"+STRING(current)+" Observation Parameters",
    {"UTC Date:","DR Lat:","DR Lon:","Body:","Artificial:","Average:"},
    {"UTC Date (YYYY.MMDD) (UTC Time: "+GET_UTC_TIME()+")",
     "DR Latitude ° (N=+ S=-)",
     "DR Longitude ° (N=+ S=-)",     
     "Observed body and limb",
     "Check if using an artificial horizon",
     "Perform observation averaging for HS"});
  IF NOT c THEN RETURN c; END;

  CASE
    IF bodyItem(current)=1 THEN sightType(current):=1; limbSign(current):=1; END;
    IF bodyItem(current)=2 THEN sightType(current):=1; limbSign(current):=2; END;
    IF bodyItem(current)=3 THEN sightType(current):=5; END;
    IF bodyItem(current)=4 THEN sightType(current):=2; limbSign(current):=1; END;
    IF bodyItem(current)=5 THEN sightType(current):=2; limbSign(current):=2; END;
    IF bodyItem(current)=6 THEN sightType(current):=6; END;
    IF (bodyItem(current)>=7) AND (bodyItem(current)<=10) THEN 
      sightType(current):=4; 
      starId(current):=bodyItem(current)-6; 
    END;
    IF bodyItem(current)=10 THEN sightType(current):=7; END;
    IF bodyItem(current)>11 THEN 
      sightType(current):=3; 
      starId(current):=bodyItem(current)-11; 
    END;
    DEFAULT
      RETURN 0; 
  END;

  DRlong_deg(current):=NORMALIZE_LONGITUDE(DRlong_deg(current));
  obsDate(current):=tempDate;
  date_default:=obsDate(current);
  DRlat_default:=DRlat_deg(current);
  DRlon_default:=DRlong_deg(current);  
  
  // If the pre-calculated option was selected,
  // skip the corrections and sextant altitude screens
  IF sightType(current)=7 THEN RETURN 1; END;

  // OBSERVATION CORRECTIONS

  IF IC_min(current)=0 THEN IC_min(current):=IC_default; END;
  IF HE_ft(current)=0 THEN HE_ft(current):=HE_default; END;
  IF temp_C(current)=0 THEN temp_C(current):=temp_default; END;
  IF press_mb(current)=0 THEN press_mb(current):=press_default; END;
  
  c:=INPUT(
    {IC_min(current),
     HE_ft(current),
     horShort_nm(current),
     temp_C(current),
     press_mb(current)},
    "LOP "+STRING(current)+": Corrections",
    {"IC:","HE:","Dip short:","Temp:","Pressure:"},
    {"Index Correction (') (+=Off -=On scale)",
     "Eye Height (ft.)",
     "Horizon distance (nm) (0 if true horizon)",
     "Temperature (°C)",
     "Pressure (mb)"});

  IF NOT c THEN RETURN c; END;
  
  IC_default:=IC_min(current);
  HE_default:=HE_ft(current);
  temp_default:=temp_C(current);
  press_default:=press_mb(current);

  // OBSERVATION READINGS

  IF avgReadings=1 THEN
    c:=AVERAGE_READINGS();
    IF SIZE(c)>0 THEN
      tempTime:=c(1);
      IF tempTime>24 THEN
        tempTime:=tempTime-24;
      ELSE
        IF tempTime<0 THEN
          tempTime:=tempTime+24;
        END;
      END;
      obs_UTC(current):=tempTime;
      HS_deg(current):=c(2);
    END;
  END;

  IF obs_UTC(current)=0 THEN obs_UTC(current):=tempTime; END;

  c:=INPUT({
    obsDate(current),
    obs_UTC(current),
    HS_deg(current)},
    "LOP #"+current+" "+bodiesList(bodyItem(current))+" Observation",
    {"Date:","Time:","HS:"},
    {"UTC Date (YYYY.MMDD)",
     "UTC Time (HH°MM'SS\")",
     "Sextant Height (DD°MM'SS\") - Uncorrected"}
  );
  
  date_default:=obsDate(current);
  
  HA_CALC(current);  
  RETURN c;
END;


HA_CALC(index)
BEGIN

  // If a horizon distance is specified,
  // use dip short formula
  IF horShort_nm(index) > 0 THEN
    dip_min(index):=(0.0416*horShort_nm(index))+(0.566*HE_ft/horShort_nm(index));
  ELSE
    dip_min(index):=0.97*√(HE_ft(index));
  END;
  IF artificial(index)==1 THEN
    HA_deg(index):=(HS_deg(index)+IC_min(index)/60)/2;
  ELSE
    HA_deg(index):=HS_deg(index)+(IC_min(index)-dip_min(index))/60;
  END;

  RETURN HA_deg(index);
END;

LOCAL REFRACTION(index)
BEGIN
  LOCAL P:=press_mb(index);
  LOCAL T:=temp_C(index);
  LOCAL a:=HA_deg(index);  
  LOCAL K:=273;  
  
  // Based on Bennett, G. G., J. Inst. Nav., vol. 30, p. 255
  // From https://navlist.net/img/104673.refraction.pdf
  RETURN 60*0.0167/TAN(a+7.31/(a+4.4))*0.28*P/(T+K);  
END;

HO_CALC_SUN(index)
BEGIN

  limb(index):=(limbSign(index)-1)*2-1;
  semiD(index):=16+0.3*COS(0.9856*(DoY(obsDate(index))-4));
  refr(index):=REFRACTION(index);
  paralX(index):=0.15*COS(HA_deg(index));
  
  altCorr_min(index):=-refr(index)-limb(index)*semiD(index)+paralX(index);

  HO_deg(index):=HA_deg(index)+altCorr_min(index)/60;
  
  RETURN HO_deg;
END;

HC_CALC(index)
BEGIN

  LOCAL c:=HC_AZ(GHA_deg(index),
     dec_deg(index),
     DRlat_deg(index),
     DRlong_deg(index));
  
  Hc_deg(index):=c(1);
  Zn_deg(index):=c(2);
  LHA_deg(index):=c(3);
  
  IF Hc_deg(index)<0 THEN
    IF NOT MSGBOX("Hc is negative."+nl+
      "Unless you're looking through"+nl+
      "the earth, check your assumed"+nl+
      "position.",1) THEN RETURN 0; END;
  END;

  // Calculate distance offset from
  // assumed position in nautical miles
  a_nm(index):=(HO_deg(index)-Hc_deg(index))*60;

  RETURN Hc_deg(index);
END;

LOCAL HC_AZ(gha,dec,lat,lon)
BEGIN
  // Calculate LHA from GHA 
  // based on longitude
  LOCAL lha:=(gha+lon) MOD 360;

  // Compute sign of declination and LHA 
  // to use Hc formula 
  LOCAL decSign:=SIGN(dec)*SIGN(lat);  
  LOCAL lhaSign:=−SIGN(lha-180);

  LOCAL hc:=ASIN(SIN(decSign*ABS(dec))
               *SIN(ABS(lat))
              +COS(decSign*ABS(dec))
               *COS(ABS(lat))
               *COS(lha)
          );

  LOCAL z:=ATAN2(SIN(lhaSign*lha),
              (COS(ABS(lat))
               *TAN(decSign*ABS(dec))
               -SIN(ABS(lat))
                *COS(lha))
          );

  // Adjust Z if negative
  IF z≤0 THEN
    z:=z+180;
  END;

  // Calculate azimuth to body based
  // on Z, latitude, and LHA
  IF lat≥0 THEN
    IF lha≥180 THEN
      z:=z;
    ELSE
      z:=360-z;
    END;
  ELSE
    IF lha≥180 THEN
      z:=180-z;
    ELSE
      z:=180+z;
    END;
  END; 

  RETURN {hc,z,lha};
END;

NORMALIZE_LONGITUDE(lon)
BEGIN
  LOCAL l:=lon MOD 360;
  IF l>180 THEN l:=l-360; END;
  RETURN l;
END;

// Returns the day number in the year for a
// given date. Used in calculations of sun semi-diameter.
DoY(date)
BEGIN
  LOCAL M:=IP(FP(date)*100);
  LOCAL D:=IP(FP(date*100)*100);

  IF M≤2 THEN
    RETURN IP(30.6*M+368.8)+D-399;
  ELSE
    RETURN IP(30.6*M+1.6)+D-34;
  END;
END;

// Returns the GHA of Aries for a given date and time
// See the AstroCalc program for a more accurate formula
// taking into account nutation of longitude
LOCAL GHAAriesApprox(date,time)
BEGIN
  LOCAL J:=jdt(date,time)-2451545;
  LOCAL T:=J/36525;
  
  RETURN (280.46061837
          +360.98564736629*J
          +0.000387933*T^2
          +T^3/38710000) MOD 360;
END;

EXPORT SOLAR_IC()
BEGIN
  LOCAL on_scale,off_scale;
  
  HAngle:=1;
  
  PRINT();
  PRINT("This will set the index correction to");
  PRINT("a value calculated from a measurement of");
  PRINT("the diameter of the sun. First, set the");
  PRINT("reflected sun so its top touches the bottom");
  PRINT("of the observed sun and record that in the");
  PRINT("BELOW field. Then continue to move the");
  PRINT("reflected sun up until its bottom touches");
  PRINT("the top of the observed sun and record the");
  PRINT("direct sextant reading (no 60 minus) in the");
  PRINT("ABOVE value.");
  PRINT(" ");
  PRINT("Press any key to continue...");
  WAIT(-1);
  
  LOCAL c:=INPUT({on_scale,off_scale},
    "Solar Index Correction Calc",
  {"Below:","Above:"},
  {"Reflected sun below actual sun (')",
   "Reflected sun above actual sun (')"});
  
  IF NOT c THEN RETURN("Canceled."); END;
  
  off_scale:=60-off_scale;
  LOCAL ic:=ABS((on_scale-off_scale)/2);
  
  IF on_scale>off_scale THEN ic:=-1*ic; END;
  
  PRINT();  
  LOCAL d:=SunEphemeris(Date,Time,1);
  LOCAL sd:=(on_scale+off_scale)/4;
  PRINT("Index correction: "+STRING(ic,2,1)+"'");
  PRINT("Measured sun semi-diameter: "+STRING(sd,2,1)+"'");
  PRINT("Actual sun semi-diameter: "+STRING(d(4)/60,2,2)+"'");
  PRINT("Difference: "+STRING(sd-d(4)/60,2,2)+"'");
  PRINT(" ");
  PRINT("Press any key to continue...");
  WAIT(-1);
  
  CHOOSE(c,"Accept results?",
         "Accept","Reject");
     
  IF c==1 THEN
    LOCAL i;
    FOR i FROM 1 TO 10 DO
    IF IC_min(i)==0 THEN IC_min(i):=ic; END;
  END;
  RETURN "Index correction set.";
  END;
  RETURN "Canceled.";
END;

EXPORT SET_TIME()
BEGIN
  LOCAL time:=Time;
  LOCAL c;
  
  c:=INPUT({time,timeZone},
           {"Set Calculator Clock"},
       {"Local time:","UTC offset:"},
       {"Current local time (HH°MM'SS\")",
        "Hours from UTC (HH°MM') (-:West +:East)"});
      
  IF NOT c THEN RETURN "Canceled."; END;
  
  Time:=time;
  
  RETURN "Local time:"+Time+" UTC Time:"+GET_UTC_TIME();
END;

EXPORT RESET_SIGHTS()
BEGIN
  LOCAL init:={0,0,0,0,0,0,0,0,0,0};
  LOCAL init1:={1,1,1,1,1,1,1,1,1,1};
  HAngle:=1;
  current           := 1;
  
  dip_min           := init;
  horShort_nm       := init;
  HE_ft             := init;
  HS_deg            := init; 
  artificial        := init;
  temp_C            := init;
  press_mb          := init;
  HO_deg            := init;
  HA_deg            := init;   
  DRlat_deg         := init;
  DRlong_deg        := init;
  IC_min            := init;
  altCorr_min       := init;
  obsDate           := init;
  obs_UTC           := init;
  dec_deg           := init;
  decBefore_utc     := init;
  decAfter_utc      := init;
  GHABefore_UTC     := init;
  GHA_deg           := init;
  LHA_deg           := init;
  Hc_deg            := init;
  Z_deg             := init;
  Zn_deg            := init;
  a_nm              := init;
  limbSign          := init;
  limb              := init;
  refr              := init;
  semiD             := init;
  paralX            := init; 
  rfDist_nm         := init;
  rfBrg_deg         := init;
  SHA_deg           := init;
  starId            := init;
  obsDate           := init;
  GHATimeCorr_deg   := init;
  GHAvcorr_min      := init;
  decdcorr_min      := init;
  HPcorr_min        := init;
  tableGHA_deg      := init;
  tableDec_deg      := init;
  sightType         := init;
  bodyItem          := init1;

  date_default:=Date;
  IF GET_UTC_TIME()>24 THEN
    date_default:=DATEADD(date_default,1);
  ELSE 
    IF GET_UTC_TIME()<0 THEN
      date_default:=DATEADD(date_default,-1);
    END;
  END;  

  temp_default:=20;
  press_default:=1010;

  minElevAngle_deg:=15;
  minAz_deg:=0;
  maxAz_deg:=360;
  
  LOCAL i;
  FOR i FROM 0 TO 9 DO
    EXPR("Function.F"+i+":=\"\"");
  Function.UNCHECK(i);
  END;
  
  RETURN "Reset complete";
END; 

// Returns the coordinates {longitude, latitude} of
// a point located d nautical miles from coordinates
// (latitude y1, longitude x1) on bearing of z degrees
// true.
// Inputs:  y0: initial latitude
//          x0: initial longitude
//      d : distance in nm
//      z : bearing in true degrees
//
// Returns: {longitude,latitude} : a list 
//          with the calculated coordinates

LOCAL atdz(y0,x0,d,z)
BEGIN
  LOCAL y1,x1,R;

  R:=3440.065;
   
  y1:=ASIN(SIN(y0)*COS((d/R)*(180/π))
      +COS(y0)*SIN((d/R)*(180/π))*COS(z));
  
  x1:=x0+ARG(i*SIN(z)*SIN((d/R)*(180/π))*COS(y0)+(COS((d/R)*(180/π))-SIN(y0)*SIN(y1)));
   
  RETURN {y1,x1};
END;
 
LOCAL jdt(date,time)
BEGIN
  RETURN DDAYS(2000.0101,date)+2451545
         +(time-12°0′0)/24;
END;

LOCAL jd(date)
BEGIN
  RETURN jdt(date,12);
END;

// Converts a degree/minute/second number D to
// a string formatted as  degrees and
// decimal minutes: DD°MM.M'
LOCAL →DMD(D)
BEGIN
  LOCAL d:=HMS→(D);

  RETURN STRING(IP(d))+"°"
     +STRING(ROUND(FP(ABS(d))*600)/10,2,1)+"'";
END;

LOCAL HMS(H)
BEGIN
  LOCAL d:=HMS→(H);
  RETURN IP(d)+":"+IP(FP(d)*60)+":"
    +IP(FP(d*60)*60);
END;

LOCAL ATAN2(y, x)
BEGIN
  RETURN ARG(x + i*y);
END;

LOCAL GET_UTC_TIME()
BEGIN
  RETURN →HMS(Time - timeZone);
END;

GET_SOFTKEY(keylist,flags)
BEGIN
  LOCAL k:=keylist,i;
  FOR i FROM 1 TO SIZE(flags) DO
    IF flags(i) THEN k(i):=CHAR(8226)+k(i); END;
  END;
  WHILE 1 DO
    DRAWMENU(k);
    LOCAL r:=WAIT(-1);
    // Check if we have a keyboard or
    // mouse event
    IF TYPE(r)=0 THEN
      // We have a keyboard event. Check that
      // either ESC or ON was pressed and
      // return or do nothing
      IF r=4 OR r=46 THEN
        RETURN 0;
      END;
    ELSE 
      // We have a mouse event.
      // Check that we have a mouse 
      // click event 
      IF r(1)=3 THEN
        // Check that the click was
        // on the soft key bar at the
        // bottom
        IF r(3)>220 THEN
          // Check that the soft key
          // has a label attached to
          // it or do nothing
          LOCAL key:=IP(r(2)/53)+1;
          IF keylist(key)<>"" THEN
            RETURN key;
          END;
        END;
      END;
    END;
  END;
END;
